// Copyright Epic Games, Inc. All Rights Reserved.

#include "frontend.h"

#include <zencore/endian.h>
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
#include <zencore/string.h>

ZEN_THIRD_PARTY_INCLUDES_START
#if ZEN_PLATFORM_WINDOWS
#	include <Windows.h>
#endif
ZEN_THIRD_PARTY_INCLUDES_END

static unsigned char gHtmlZipData[] = {
#include <html.zip.h>
};
namespace zen {

////////////////////////////////////////////////////////////////////////////////
HttpFrontendService::HttpFrontendService(std::filesystem::path Directory) : m_Directory(Directory)
{
	std::filesystem::path SelfPath = GetRunningExecutablePath();

	// Locate a .zip file appended onto the end of this binary
	IoBuffer HtmlZipDataBuffer(IoBuffer::Wrap, gHtmlZipData, sizeof(gHtmlZipData) - 1);
	m_ZipFs = ZipFs(std::move(HtmlZipDataBuffer));

	if (m_Directory.empty() && !m_ZipFs)
	{
		// Probe for development layout

		std::error_code		  ErrorCode;
		std::filesystem::path Path = SelfPath;

		while (Path.has_parent_path())
		{
			std::filesystem::path ParentPath = Path.parent_path();
			if (ParentPath == Path)
			{
				break;
			}
			if (std::filesystem::is_regular_file(ParentPath / "xmake.lua", ErrorCode))
			{
				if (ErrorCode)
				{
					break;
				}

				std::filesystem::path HtmlDir = ParentPath / "src" / "zenserver" / "frontend" / "html";

				if (std::filesystem::is_directory(HtmlDir, ErrorCode))
				{
					m_Directory = HtmlDir;
				}
				break;
			}
			Path = ParentPath;
		}
	}

	if (m_ZipFs)
	{
		ZEN_INFO("front-end is served from embedded zip");
	}
	else if (!m_Directory.empty())
	{
		ZEN_INFO("front-end is served from '{}'", m_Directory);
	}
	else
	{
		ZEN_INFO("front-end is NOT AVAILABLE");
	}
}

HttpFrontendService::~HttpFrontendService()
{
}

const char*
HttpFrontendService::BaseUri() const
{
	return "/dashboard/";  // in order to use the root path we need to remove HttpAddUrlToUrlGroup in HttpSys.cpp
}

////////////////////////////////////////////////////////////////////////////////
void
HttpFrontendService::HandleRequest(zen::HttpServerRequest& Request)
{
	using namespace std::literals;

	std::string_view Uri = Request.RelativeUriWithExtension();
	for (; Uri.length() > 0 && Uri[0] == '/'; Uri = Uri.substr(1))
		;
	if (Uri.empty())
	{
		Uri = "index.html"sv;
	}

	// Dismiss if the URI contains .. anywhere to prevent arbitrary file reads
	if (Uri.find("..") != Uri.npos)
	{
		return Request.WriteResponse(HttpResponseCode::Forbidden);
	}

	// Map the file extension to a MIME type. To keep things constrained, only a
	// small subset of file extensions is allowed

	HttpContentType ContentType = HttpContentType::kUnknownContentType;

	if (const size_t DotIndex = Uri.rfind("."); DotIndex != Uri.npos)
	{
		const std::string_view DotExt = Uri.substr(DotIndex + 1);

		ContentType = ParseContentType(DotExt);
	}

	if (ContentType == HttpContentType::kUnknownContentType)
	{
		return Request.WriteResponse(HttpResponseCode::Forbidden);
	}

	// The given content directory overrides any zip-fs discovered in the binary
	if (!m_Directory.empty())
	{
		FileContents File = ReadFile(m_Directory / Uri);

		if (!File.ErrorCode)
		{
			return Request.WriteResponse(HttpResponseCode::OK, ContentType, File.Data[0]);
		}
	}

	if (IoBuffer FileBuffer = m_ZipFs.GetFile(Uri))
	{
		return Request.WriteResponse(HttpResponseCode::OK, ContentType, FileBuffer);
	}

	Request.WriteResponse(HttpResponseCode::NotFound, HttpContentType::kText, "Not found"sv);
}

}  // namespace zen
